Skip to content

Host the Merge-Bot in the Hub and Measure Fleet Workflow Reuse - #744

Merged
ptr727 merged 4 commits into
developfrom
feature/reusable-workflows
Aug 15, 2026
Merged

Host the Merge-Bot in the Hub and Measure Fleet Workflow Reuse#744
ptr727 merged 4 commits into
developfrom
feature/reusable-workflows

Conversation

@ptr727

@ptr727 ptr727 commented Aug 15, 2026

Copy link
Copy Markdown
Owner

What

The first step of moving the fleet's standard workflows out of every repo and into the hub, once, as workflow_call tasks a downstream repo reaches through a pinned uses: rather than carries. Repo-specific behavior lands in a composite-action hook at a conventional path, with a hub default where one makes sense.

  • docs/reusable-workflows.md (hub-only): the target model, the hook contract, the pin and secrets policy, the hook catalog, the Docker family design, the migration phases, the merge-bot adoption stub, what a pilot proves, and the open decisions.
  • spec/workflow_reuse.py and reports/workflow-reuse.md: the burn-down. It reads every cataloged repo's .github/workflows/ from its ground-truth branch, compares each file against the hub canonical after the verbatim engine's normalization, and clusters the copies into variants. First run at hub 7c67328: 108 files and 10,964 lines across 20 downstream repos, 5,385 lines (49 percent) byte-identical to a hub canonical, one file reaching the hub. Its --selftest runs in CI beside the audit engine's.
  • .github/workflows/merge-bot-task.yml: the merge-bot as a hub task. Three jobs, merge-dependabot, merge-app (built-in codegen and upstream-version pairs plus a rules JSON input for a repo's own tracker), and disable-auto-merge-on-maintainer-push. Inputs app-login, rules, delete-branch. Explicit secrets: declarations, no inherit. No GITHUB_TOKEN scope in the callee and permissions: {} in the caller, which is the hub half of merge-bot: GITHUB_TOKEN permissions are unused, since every write goes through the App token #521.
  • .github/workflows/merge-bot-pull-request.yml: the hub's own caller stub, byte-shaped like the downstream one apart from the ./ uses.
  • spec/files.json: the merge-bot contract becomes the caller job, the hub task token, and the two mapped secrets. Every downstream copy now reports missing required job 'merge-bot', which is the adoption work list the TODO sweep entry carries.
  • TODO.md: the "Hub-Hosted Reusable Workflows" cluster (gates, pure functions, release chain and Docker core, type-specific tasks, the catalog snippet, three open decisions, the Dockerfile shape item), the merge-bot adoption sweep, the superseded investigation entry removed, and "The Merge-Bot Token Grants" folded in.
  • Prose that described copied jobs: GOVERNANCE.md "Hub-Hosted Tooling" (one sentence, verbatim section) and the concurrency exception, WORKFLOW.md D8.3 and the D8/D9 audit line, the catalog README and the upstream tracker's bump-branch-prefix comment, the two stale "override seam" pointers in spec/fidelity-model.md and spec/audit.py, and the two skills that describe copied orchestration.

Why the catalog snippet is not in this PR

catalog/snippets/workflows/ is scanned by repo_gate.py check_sha_pin, so a caller snippet cannot carry a placeholder SHA, and no released hub commit carries merge-bot-task.yml until this promotes and releases. The stub's text is in the doc's "Adopting the Merge-Bot" and the TODO cluster carries the follow-up.

Verification

  • python3 spec/workflow_reuse.py --selftest and --report (the report is regenerated from the live fleet).
  • The merge-app rule filter run standalone against the four built-in pairs, an ha-version-bump/ prefix on develop and on main, an empty prefix, and three malformed rules inputs.
  • python3 spec/audit.py --selftest with four new interface cases for the stub, python3 -m unittest discover -s scripts/tests (668 tests, one new for an owner-scoped reusable-workflow pin), python3 spec/validate.py, python3 scripts/repo_gate.py, python3 scripts/build_dist.py --check, python3 scripts/prose_lint.py with the CI rule list, actionlint, markdownlint, cspell, editorconfig-checker, ruff check and format, mypy.
  • python3 spec/audit.py HomeAutomation-Config reports the new merge-bot finding as designed.

What this PR cannot prove

The hub's own stub proves the callee mechanics on the first Dependabot PR after this lands on develop, since under pull_request_target a feature branch's callee resolves from the base branch. What only a downstream adopter proves is cross-repository resolution of the owner-scoped uses:, Dependabot bumping a # <tag> pin on a reusable workflow, and the rules input end to end. The doc states these as the pilot's checklist rather than as claims.

Refs #521 (hub half done here, the sweep half is the adoption). Settles the workflow half of #735 by design once the phases land, and #585 and #729 land inside the gates phase.

The fleet copies its standard workflows into every repo, and a fix to a
shared job is a fleet sweep rather than one edit. This lands the design
for hosting each standard workflow once in the hub as a workflow_call
task, with a downstream caller stub and a composite-action hook for what
is genuinely repo-specific, and ships the first task, the merge-bot.

- docs/reusable-workflows.md states the target model, the hook contract,
  the pin and secrets policy, the phases, and the merge-bot adoption.
- spec/workflow_reuse.py measures the fleet against that model and writes
  reports/workflow-reuse.md, the burn-down: 108 files and 10,964 lines
  downstream today, 49 percent byte-identical to a hub canonical.
- merge-bot-task.yml hosts the three jobs with the extra bot pairs as a
  rules input, declares no GITHUB_TOKEN scope (#521's hub half), and the
  hub's own merge-bot-pull-request.yml is the first caller stub.
- The manifest contract for merge-bot-pull-request.yml is now the caller
  job, the hub task token, and the two mapped secrets, so every copied
  merge-bot reports the adoption it owes.
- TODO.md carries the phased cluster and the adoption sweep, and the two
  skills that describe copied orchestration point at the design.
Copilot AI lite review requested due to automatic review settings August 15, 2026 19:31

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR advances the fleet’s “reach, don’t carry” workflow strategy by hosting the merge-bot as a hub workflow_call task, adding a measurement tool/report to quantify workflow duplication across the fleet, and updating the audit/spec/docs to treat downstream workflows as thin caller stubs plus hooks.

Changes:

  • Introduces a hub-hosted reusable workflow for the merge-bot and converts the hub’s merge-bot entry workflow into a thin caller stub.
  • Adds spec/workflow_reuse.py plus a generated reports/workflow-reuse.md to measure workflow reuse/variants across cataloged repos, and wires the tool’s selftest into CI validation.
  • Updates audit contracts, documentation, TODO tracking, and skills text to reflect the new reusable-workflow model and merge-bot caller contract.

Reviewed changes

Copilot reviewed 22 out of 22 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
WORKFLOW.md Updates merge-bot/upstream-version tracker contract language to account for caller-provided rules.
TODO.md Reframes and expands the “Hub-Hosted Reusable Workflows” work breakdown and adoption tracking.
spec/workflow_reuse.py Adds workflow-reuse measurement tool + selftest/report rendering.
spec/files.json Updates merge-bot workflow interface contract to require a merge-bot caller job and mapped secrets.
spec/fidelity-model.md Updates fidelity model text to include bot workflows and reference reusable-workflow design doc.
spec/audit.py Adjusts interface-check messaging and adds selftests for the new merge-bot caller stub contract.
scripts/tests/test_repo_gate.py Adds a repo-gate test ensuring reusable-workflow refs resolve/pin-check correctly.
reports/workflow-reuse.md Adds generated fleet reuse/variant report produced by spec/workflow_reuse.py --report.
GOVERNANCE.md Updates “Hub-Hosted Tooling” guidance to explicitly include reusable workflows alongside actions.
docs/reusable-workflows.md Adds hub-only design doc describing the reusable-workflow migration model, hooks, phases, and merge-bot adoption stub.
catalog/snippets/workflows/README.md Documents presence of hub-hosted reusable tasks alongside orchestrators in the hub.
catalog/snippets/workflows/check-upstream-version-task.yml Updates comments/description to reference merge-bot rules (built-ins or caller-provided).
.github/workflows/validate-task.yml Runs spec/workflow_reuse.py --selftest in the CI self-test suite.
.github/workflows/merge-bot-task.yml Adds hub-hosted merge-bot reusable workflow implementing the merge/disable logic with App token.
.github/workflows/merge-bot-pull-request.yml Replaces inlined merge-bot jobs with a thin caller stub that uses: the hub task and maps secrets; sets permissions: {}.
.gitattributes Pins LF EOL for the new shebang-executed Python tool.
.editorconfig Pins LF EOL for spec/workflow_reuse.py alongside other directly-executed spec tooling.
.claude-plugin/fleet-skills/skills/workflow-ci-contract/SKILL.md Updates workflow CI contract skill text to include the reusable-workflow “reached, not carried” model.
.claude-plugin/fleet-skills/skills/operational-vs-release-workflow/references/release-publish-mechanics.md Updates guidance to reference the release-chain phase of reusable-workflow migration.
.claude-plugin/fleet-skills/.source-digest Updates skill source digest.
.agents/skills/workflow-ci-contract/SKILL.md Mirrors reusable-workflow model update in the agent skill copy.
.agents/skills/operational-vs-release-workflow/references/release-publish-mechanics.md Mirrors reusable-workflow migration reference update in the agent skill copy.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread spec/workflow_reuse.py Outdated
Comment thread docs/reusable-workflows.md Outdated
Copilot AI review requested due to automatic review settings August 15, 2026 19:38

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 22 out of 22 changed files in this pull request and generated no new comments.

Suppressed comments (2)

spec/workflow_reuse.py:223

  • The report text says variants are clustered by "pairwise similarity", but cluster() only compares each candidate to a representative (the first member) when adding it to a cluster. To avoid overstating what the clustering guarantees, the wording here should match the implementation.
    w(
        f"Downstream copies of each hub canonical. A variant is a cluster of copies at or above {CLUSTER_THRESHOLD} pairwise similarity, so the cluster count is how many distinct shapes of one workflow the fleet runs today. Callers are the copies that already reach the hub rather than carrying the job bodies."
    )

spec/workflow_reuse.py:98

  • The cluster() docstring says this is doing "single-link" clustering, but the implementation only compares each new repo to the first member of an existing cluster (members[0]). That’s a different clustering strategy and can produce clusters that are not single-link (and not necessarily pairwise-similar), which makes the description misleading.

This issue also appears on line 221 of the same file.

def cluster(copies):
    """Greedy single-link clusters of {repo: text} at CLUSTER_THRESHOLD, each a sorted repo list.

    Greedy rather than exhaustive on purpose: the count of variants is what the report needs, and a stricter
    partition would only split a borderline pair into two clusters that read the same to a maintainer.

Copilot AI review requested due to automatic review settings August 15, 2026 19:43
@ptr727

ptr727 commented Aug 15, 2026

Copy link
Copy Markdown
Owner Author

Answering the two suppressed findings, "Suppressed comments (2)", from the round on d08fea7 (#744 (review)).

  • spec/workflow_reuse.py:98, "The cluster() docstring says this is doing single-link clustering, but the implementation only compares each new repo to the first member of an existing cluster". Fixed in 210d88f: the docstring and the module summary now describe it as leader clustering, where each copy joins the first cluster whose leader it matches at the threshold and a member is guaranteed similar to its leader rather than to every other member.
  • spec/workflow_reuse.py:223, "The report text says variants are clustered by pairwise similarity". Fixed in 210d88f: the report sentence now reads "a cluster of copies each at or above 0.85 similarity to the cluster's first member", and reports/workflow-reuse.md is regenerated with it.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 22 out of 22 changed files in this pull request and generated no new comments.

Suppressed comments (4)

.github/workflows/merge-bot-task.yml:150

  • Same issue as above: inputs.delete-branch uses dot notation. Use bracket syntax for hyphenated input names so the value resolves correctly.
        env:
          BASE_REF: ${{ github.event.pull_request.base.ref }}
          DELETE_BRANCH: ${{ inputs.delete-branch }}
          PR_URL: ${{ github.event.pull_request.html_url }}
          GH_TOKEN: ${{ steps.app-token.outputs.token }}

.github/workflows/merge-bot-task.yml:165

  • inputs.app-login is referenced with dot notation in this job condition as well. Use bracket syntax so the disable-auto-merge job correctly recognizes maintainer pushes to App-authored PRs.
    if: >-
      github.event.action == 'synchronize' &&
      github.event.pull_request.head.repo.full_name == github.repository &&
      (
        github.event.pull_request.user.login == 'dependabot[bot]' ||
        github.event.pull_request.user.login == inputs.app-login
      ) &&
      github.actor != github.event.pull_request.user.login

.github/workflows/merge-bot-task.yml:79

  • inputs.delete-branch is referenced with dot notation (inputs.delete-branch). In GitHub Actions expressions, keys containing - must be accessed with bracket syntax (otherwise - is parsed as subtraction), so this will not resolve correctly and can break the --delete-branch behavior.

This issue also appears on line 146 of the same file.

        env:
          BASE_REF: ${{ github.event.pull_request.base.ref }}
          DELETE_BRANCH: ${{ inputs.delete-branch }}
          PR_URL: ${{ github.event.pull_request.html_url }}
          GH_TOKEN: ${{ steps.app-token.outputs.token }}

.github/workflows/merge-bot-task.yml:89

  • inputs.app-login is referenced with dot notation (inputs.app-login). In GitHub Actions expressions, hyphenated keys must use bracket syntax; otherwise this condition may never match and the App PR auto-merge job won’t run.

This issue also appears on line 158 of the same file.

    if: >-
      (github.event.action == 'opened' || github.event.action == 'reopened') &&
      github.event.pull_request.user.login == inputs.app-login &&
      github.event.pull_request.head.repo.full_name == github.repository

Copilot AI review requested due to automatic review settings August 15, 2026 19:47
@ptr727

ptr727 commented Aug 15, 2026

Copy link
Copy Markdown
Owner Author

Answering the four suppressed findings, "Suppressed comments (4)", from the round on 210d88f (#744 (review)). All four are one claim, that a hyphenated key cannot be read with dot notation in an expression.

  • .github/workflows/merge-bot-task.yml:79 and :150, "inputs.delete-branch is referenced with dot notation ... keys containing - must be accessed with bracket syntax (otherwise - is parsed as subtraction)". Disproven.
  • .github/workflows/merge-bot-task.yml:89 and :165, "inputs.app-login is referenced with dot notation ... hyphenated keys must use bracket syntax". Disproven.

The GitHub Actions contexts reference states the property dereference rule: the property name must start with a letter or _ and contain only alphanumeric characters, -, or _. A hyphen is inside the allowed set, and index syntax is required only for a name outside it. The same file reads steps.app-token.outputs.token with dot notation in every job, which is the same hyphenated shape and the expression the fleet's merge-bot has resolved on every Dependabot merge it has ever performed. actionlint, which rejects an invalid expression, parses the file clean. Recorded in .github/copilot-instructions.md "Disproved Claims" in b39b16f.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 23 out of 23 changed files in this pull request and generated 1 comment.

Comment thread .github/workflows/merge-bot-task.yml
@ptr727
ptr727 merged commit f33fa7e into develop Aug 15, 2026
7 checks passed
@ptr727
ptr727 deleted the feature/reusable-workflows branch August 15, 2026 20:44
ptr727 added a commit that referenced this pull request Aug 15, 2026
…leaner Report (#746)

Promote `develop` to `main`, carrying two changes:

- #744 Host the Merge-Bot in the Hub and Measure Fleet Workflow Reuse:
the hub-hosted reusable-workflow design (`docs/reusable-workflows.md`),
the fleet workflow-reuse measurement (`spec/workflow_reuse.py`,
`reports/workflow-reuse.md`), the first hub task
(`.github/workflows/merge-bot-task.yml`) with the hub's own caller stub,
the manifest contract change for `merge-bot-pull-request.yml`, and the
TODO cluster and adoption sweep.
- #739 Mark PhotoCleaner Operational After the Promotion of the b09078e
Resync: the PhotoCleaner audit report refresh.

The release that follows this promotion is the first tag naming
`merge-bot-task.yml`, which is the pin the downstream caller stubs use,
so the merge-bot adoption sweep and the catalog snippet both wait on it.
A Dependabot pull request against `main` after this merge is the
`--merge` half of the live proof of the callee.

Refs #521 (hub half shipped in #744, sweep half is the adoption). No
issue closes on this promotion.
ptr727 added a commit that referenced this pull request Aug 16, 2026
## What

- `docs/reusable-workflows.md` gains a **Rollout** section replacing the
prose "Migration Phases": six stages (0 design and merge-bot task, 1
merge-bot adoption, 2 gates, 3 pure functions, 4 release chain and
Docker core, 5 type-specific), each with hub, release, and per-repo
adoption checkboxes. Items are ticked only with the evidence that closed
them (PR, commit, tag, run URL), and the section opens with how a
session resumes from it: read it first, take the first unchecked item in
an open stage, verify against the tree, work in a worktree, tick in the
same PR. Stage 0 is ticked with #744, #746, and release `2.0.338`, apart
from the two live-proof items that wait on the next Dependabot PRs.
- `catalog/snippets/workflows/merge-bot-pull-request.yml`: the caller
stub, now that `2.0.338` on `20616e0` carries `merge-bot-task.yml`. The
pin resolves under `repo_gate.py check_sha_pin`. The doc's stub carries
the same pin, and the catalog README gains the row.
- `TODO.md`: the cluster names the Rollout section as the state tracker
and drops the snippet entry it just shipped.

## Why

The plan lived as prose and as TODO entries, and nothing in git said
which stage was done, so a cold session had no first unchecked item to
take. Only git persists.

## Verification

prose_lint (CI rule list), actionlint, markdownlint, `repo_gate.py` (the
new pin resolved against GitHub), unit tests, audit self-test,
build_dist --check.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants